Fix DurationFormatUtils.formatPeriod() calculation when pattern omits 'M' - #1780
Conversation
|
@Alwaysgaurav1 |
8b50bf4 to
35ada86
Compare
|
@garydgregory Apologies for the oversight. I've fixed the Checkstyle issues and verified the build passes locally with |
|
There is one case this PR breaks that I just added to demonstrate the issue: @Test
void testFormatPeriodWithoutMonthsAfterLeapDayAnniversary() {
final TimeZone timeZone = TimeZone.getTimeZone("UTC");
final Calendar start = Calendar.getInstance(timeZone);
start.clear();
// 2020 was not a leap year
start.set(2020, Calendar.FEBRUARY, 29);
final Calendar end = Calendar.getInstance(timeZone);
end.clear();
// 2021 was not a leap year
end.set(2021, Calendar.MARCH, 1);
assertEquals("1 years 1 days", DurationFormatUtils.formatPeriod(start.getTimeInMillis(), end.getTimeInMillis(), "y' years 'd' days'", false, timeZone));
}There is no standard for normalizing dates in non-leap years. I'm not sure if switching the direction will have unintended consequence. WDYT? Or, is it possible for the PR to keep the current normalization? |
|
Thanks for catching this edge case, @garydgregory! Root CauseThe issue occurred because SolutionWe don't need to change the normalization direction. Instead, we can unify the calculation with the rest of the token cascading hierarchy (
|
|
@Alwaysgaurav1 |
35ada86 to
2e5b6cb
Compare
|
@garydgregory Rebased on Root Cause of the Test FailureIn the previous commit, SolutionWe keep the exact same normalization behavior by unifying how omitted tokens are cascaded (
|
|
Thanks @Alwaysgaurav1 , merged 🚀 |
'M' (#1780). - Fix inline comment. - Sort members. - Remove extra blank line at EOF.
Description
Thanks for your contribution to Apache Commons! Your help is appreciated!
Before you push a pull request, review this list:
mvn) before submitting your pull request to ensure all build checks and tests pass.Fixes a calculation bug in
DurationFormatUtils.formatPeriod(long, long, String, boolean, TimeZone)where duration formatting with patterns containing year (y) and day (d) tokens without month (M) tokens (e.g."y' years 'd' days'"or"y'y 'd'd'") incorrectly inflates the duration by +1 full year (+365 days) when spanning across a calendar year boundary that is less than a full 12-month anniversary.Problem / Reproduction
Prior to this fix:
2024-12-15to2025-01-15) was formatted as"1 years 31 days"(396 days instead of 31 days).2024-01-15to2025-01-10) was formatted as"1 years 26 days"(392 days instead of 361 days).2024-02-29to2025-02-28) was formatted as"1 years 28 days"(393 days instead of 365 days).Root Cause
yearswas initialized asend.get(Calendar.YEAR) - start.get(Calendar.YEAR).Mwas not present intokens, the code rolled month differences intodays, but did not decrementyearswhen less than a full 12-month calendar year had elapsed.startwas not advanced to match the subtractedyears, which caused subsequent month-to-day accumulations to miscount intervening days across year boundaries.Solution
Mis omitted andyis present:months < 0 || (months == 0 && days < 0)). If not, decrementyears.startby the elapsedyears(start.add(Calendar.YEAR, (int) years)).daysuntilstart.get(YEAR) == end.get(YEAR) && start.get(MONTH) == end.get(MONTH).daysfromstart.getActualMaximum(Calendar.DAY_OF_MONTH).Tests Added & Verification
testFormatPeriodWithoutMonths()toDurationFormatUtilsTest.javaverifying: